// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/win/i18n.h"

#include <windows.h>

#include "base/logging.h"
#include "base/macros.h"

namespace {

// Keep this enum in sync with kLanguageFunctionNames.
enum LanguageFunction {
    SYSTEM_LANGUAGES,
    USER_LANGUAGES,
    PROCESS_LANGUAGES,
    THREAD_LANGUAGES,
    NUM_FUNCTIONS
};

const char kSystemLanguagesFunctionName[] = "GetSystemPreferredUILanguages";
const char kUserLanguagesFunctionName[] = "GetUserPreferredUILanguages";
const char kProcessLanguagesFunctionName[] = "GetProcessPreferredUILanguages";
const char kThreadLanguagesFunctionName[] = "GetThreadPreferredUILanguages";

// Keep this array in sync with enum LanguageFunction.
const char* const kLanguageFunctionNames[] = {
    &kSystemLanguagesFunctionName[0],
    &kUserLanguagesFunctionName[0],
    &kProcessLanguagesFunctionName[0],
    &kThreadLanguagesFunctionName[0]
};

static_assert(NUM_FUNCTIONS == arraysize(kLanguageFunctionNames),
    "LanguageFunction enum and kLanguageFunctionNames array must be "
    "kept in sync");

// Calls one of the MUI Get*PreferredUILanguages functions, placing the result
// in |languages|.  |function| identifies the function to call and |flags| is
// the function-specific flags (callers must not specify MUI_LANGUAGE_ID or
// MUI_LANGUAGE_NAME).  Returns true if at least one language is placed in
// |languages|.
bool GetMUIPreferredUILanguageList(LanguageFunction function, ULONG flags,
    std::vector<wchar_t>* languages)
{
    DCHECK(0 <= function && NUM_FUNCTIONS > function);
    DCHECK_EQ(0U, (flags & (MUI_LANGUAGE_ID | MUI_LANGUAGE_NAME)));
    DCHECK(languages);

    HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
    if (NULL != kernel32) {
        typedef BOOL(WINAPI * GetPreferredUILanguages_Fn)(
            DWORD, PULONG, PZZWSTR, PULONG);
        GetPreferredUILanguages_Fn get_preferred_ui_languages = reinterpret_cast<GetPreferredUILanguages_Fn>(
            GetProcAddress(kernel32, kLanguageFunctionNames[function]));
        if (NULL != get_preferred_ui_languages) {
            const ULONG call_flags = flags | MUI_LANGUAGE_NAME;
            ULONG language_count = 0;
            ULONG buffer_length = 0;
            if (get_preferred_ui_languages(call_flags, &language_count, NULL,
                    &buffer_length)
                && 0 != buffer_length) {
                languages->resize(buffer_length);
                if (get_preferred_ui_languages(call_flags, &language_count,
                        &(*languages)[0], &buffer_length)
                    && 0 != language_count) {
                    DCHECK(languages->size() == buffer_length);
                    return true;
                } else {
                    DPCHECK(0 == language_count)
                        << "Failed getting preferred UI languages.";
                }
            } else {
                DPCHECK(0 == buffer_length)
                    << "Failed getting size of preferred UI languages.";
            }
        } else {
            DVLOG(2) << "MUI not available.";
        }
    } else {
        NOTREACHED() << "kernel32.dll not found.";
    }

    return false;
}

bool GetUserDefaultUILanguage(std::wstring* language, std::wstring* region)
{
    DCHECK(language);

    LANGID lang_id = ::GetUserDefaultUILanguage();
    if (LOCALE_CUSTOM_UI_DEFAULT != lang_id) {
        const LCID locale_id = MAKELCID(lang_id, SORT_DEFAULT);
        // max size for LOCALE_SISO639LANGNAME and LOCALE_SISO3166CTRYNAME is 9
        wchar_t result_buffer[9];
        int result_length = GetLocaleInfo(locale_id, LOCALE_SISO639LANGNAME, &result_buffer[0],
            arraysize(result_buffer));
        DPCHECK(0 != result_length) << "Failed getting language id";
        if (1 < result_length) {
            language->assign(&result_buffer[0], result_length - 1);
            region->clear();
            if (SUBLANG_NEUTRAL != SUBLANGID(lang_id)) {
                result_length = GetLocaleInfo(locale_id, LOCALE_SISO3166CTRYNAME, &result_buffer[0],
                    arraysize(result_buffer));
                DPCHECK(0 != result_length) << "Failed getting region id";
                if (1 < result_length)
                    region->assign(&result_buffer[0], result_length - 1);
            }
            return true;
        }
    } else {
        // This is entirely unexpected on pre-Vista, which is the only time we
        // should try GetUserDefaultUILanguage anyway.
        NOTREACHED() << "Cannot determine language for a supplemental locale.";
    }
    return false;
}

bool GetPreferredUILanguageList(LanguageFunction function, ULONG flags,
    std::vector<std::wstring>* languages)
{
    std::vector<wchar_t> buffer;
    std::wstring language;
    std::wstring region;

    if (GetMUIPreferredUILanguageList(function, flags, &buffer)) {
        std::vector<wchar_t>::const_iterator scan = buffer.begin();
        language.assign(&*scan);
        while (!language.empty()) {
            languages->push_back(language);
            scan += language.size() + 1;
            language.assign(&*scan);
        }
    } else if (GetUserDefaultUILanguage(&language, &region)) {
        // Mimic the MUI behavior of putting the neutral version of the lang after
        // the regional one (e.g., "fr-CA, fr").
        if (!region.empty())
            languages->push_back(std::wstring(language)
                                     .append(1, L'-')
                                     .append(region));
        languages->push_back(language);
    } else {
        return false;
    }

    return true;
}

} // namespace

namespace base {
namespace win {
    namespace i18n {

        bool GetUserPreferredUILanguageList(std::vector<std::wstring>* languages)
        {
            DCHECK(languages);
            return GetPreferredUILanguageList(USER_LANGUAGES, 0, languages);
        }

        bool GetThreadPreferredUILanguageList(std::vector<std::wstring>* languages)
        {
            DCHECK(languages);
            return GetPreferredUILanguageList(
                THREAD_LANGUAGES, MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK,
                languages);
        }

    } // namespace i18n
} // namespace win
} // namespace base
